package com.jie.server.core.util;

import com.jie.common.exception.entity.JieException;
import com.jie.common.http.entity.response.HttpResponse;
import com.jie.common.message.constant.MessageConstant;
import com.jie.common.message.entity.Message;
import com.jie.common.util.BytesUtil;
import com.jie.server.core.ServerApplication;
import com.jie.server.core.constant.ChannelAttachmentStatusConstant;
import com.jie.server.core.entity.ChannelAttachment;
import com.jie.server.core.entity.SessionNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;

/**
 * 服务端用于nio读写的工具类
 * @author XieZhiJie
 * @date 2022/04/22 11:28
 */
public class NioUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(NioUtil.class);

    /**
     * 读取一个报文, 从报文头中得知报文长度, 并读取报文完整信息, 不包含报文头
     * @param channel        channel
     * @param readByteBuffer readByteBuffer
     * @return               返回读取到的字节数组
     * @throws IOException   IOException
     */
    public static byte[] readMessage(SocketChannel channel, ByteBuffer readByteBuffer) throws IOException {
        return read(channel, readByteBuffer, BytesUtil.transformBytesToInt(read(channel, readByteBuffer, MessageConstant.MESSAGE_HEAD_LENGTH)));
    }

    /**
     * 用ByteBuffer对象读出length字节数据
     * @param channel        channel
     * @param readByteBuffer readByteBuffer
     * @param length         要读出的长度
     * @return               返回length长度装填了数据的数组
     */
    public static byte[] read(SocketChannel channel, ByteBuffer readByteBuffer, int length) throws IOException {
        // 假设目前byteBuffer里还有未读完的数据(position指向最后一个未读完数据的后一个位置, limit指向最后)
        byte[] results = new byte[length];

        // 1.如果position大于等于length, 直接读完即可
        if (readByteBuffer.position() >= length) {
            readByteBuffer.flip();
            readByteBuffer.get(results, 0, length);
        } else {
            // 2.如果没读完, 则先将其读到结果数组中, 再重新读, 然后继续这个过程
            int start = 0;
            while (channel.read(readByteBuffer) > 0) {
                readByteBuffer.flip();
                int readLength = Math.min(length, readByteBuffer.limit());
                // 不断读, 看了下这个方法的(ReadableByteChannel里的)的read的注释, 读完后
                // position位置移动position+n-1
                readByteBuffer.get(results, start, readLength);
                if (readLength == length) {
                    // 如果读完了, 那么
                    break;
                } else {
                    // 还没读完
                    length -= readLength;
                    start += readLength;
                    // 清除掉所有数据, 准备再读
                    readByteBuffer.flip();
                }
            }
        }

        // 3.将未读完的数据移到前面去, 下次读
        readByteBuffer.compact();
        return results;
    }

    /**
     * 写一个封装了报文头的包出去
     * @param channel         channel
     * @param writeByteBuffer 通过writeByteBuffer写数据
     * @param message         message
     * @throws IOException    IOException
     */
    public static void writeMessage(SocketChannel channel, ByteBuffer writeByteBuffer, Message message) throws IOException {
        byte[] lengthBytes = BytesUtil.transformIntToBytes(message.getMessageBytes().length);
        byte[] sendBytes = new byte[lengthBytes.length + message.getMessageBytes().length];
        System.arraycopy(lengthBytes, 0, sendBytes, 0, lengthBytes.length);
        System.arraycopy(message.getMessageBytes(), 0, sendBytes, lengthBytes.length, message.getMessageBytes().length);
        message.setMessageBytes(sendBytes);
        write(channel, writeByteBuffer, message);
    }

    /**
     * 将一个报文写出去
     * @param channel         channel
     * @param writeByteBuffer 通过writeByteBuffer写数据
     * @param message         报文
     * @throws IOException    IOException
     */
    public static void write(SocketChannel channel, ByteBuffer writeByteBuffer, Message message) throws IOException {
        int start = 0;
        int length = Math.min(message.getMessageBytes().length, writeByteBuffer.capacity());
        int remain = message.getMessageBytes().length;
        do {
            writeByteBuffer.clear();
            writeByteBuffer.put(message.getMessageBytes(), start, length);
            writeByteBuffer.flip();
            channel.write(writeByteBuffer);
            start = start + length;
            remain -= length;
            length = Math.min(remain, writeByteBuffer.capacity());
        } while (length > 0);
    }

    /**
     * 关闭key的channel并cancel
     * @param key key
     */
    public static void closeKeyChannelAndCancel(SelectionKey key) {
        try {
            if (key != null) {
                // 删除session中的对象
                ServerApplication.SESSION.remove(((ChannelAttachment) key.attachment()).id);
                key.channel().close();
                key.cancel();
            }
        } catch (Exception e) {
            LOGGER.error(e.getMessage());
        }
    }

    /**
     * 根据id发送httpResponse
     * @param id            id
     * @param httpResponse  response
     * @throws JieException 异常
     */
    public static void sendMessage(Integer id, HttpResponse httpResponse) throws JieException {
        sendMessage(id, new Message(httpResponse.getMessageBytes()));
    }

    /**
     * 根据id从session中获取到key并发送信息
     * @param id            用户id
     * @param message       要发送的数据
     * @throws JieException 异常
     */
    public static void sendMessage(Integer id, Message message) throws JieException {
        SessionNode sessionNode = ServerApplication.SESSION.get(id);
        if (sessionNode != null && sessionNode.getKey() != null) {
            sendMessage(sessionNode.getKey(), message);
        }
    }

    /**
     * 将数据挂到写队列上
     * @param key     key
     * @param message 要发送的数据
     */
    public static void sendMessage(SelectionKey key, Message message) throws JieException {
        try {
            ChannelAttachment channelAttachment = (ChannelAttachment) key.attachment();
            synchronized (channelAttachment.lock) {
                if (channelAttachment.lock.status == ChannelAttachmentStatusConstant.NOT_IN_WRITE_STATUS) {
                    // 不处于监听写 与 正在写 的状态
                    // 设置为正在监听写的状态
                    channelAttachment.lock.status = ChannelAttachmentStatusConstant.LISTEN_WRITE_STATUS;
                    channelAttachment.writeQueue.add(message);
                    // 监听写事件(因为此时不在监听当前的读事件, 因此不需要判断是否需要将读事件加入)
                    key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
                } else if (channelAttachment.lock.status == ChannelAttachmentStatusConstant.LISTEN_WRITE_STATUS) {
                    // 如果正在监听 或者 正在写, 那么直接往队列加入信息
                    // 队列不需要上锁, 因为队列的操作需要通过status上锁后才操作
                    channelAttachment.writeQueue.add(message);
                } else if (channelAttachment.lock.status == ChannelAttachmentStatusConstant.WRITING_STATUS) {
                    // 正在写
                    channelAttachment.blockQueue.add(message);
                }
            }
        } catch (Exception e) {
            LOGGER.error(e.getMessage());
            throw new JieException("添加写信息失败!");
        }
    }

}
